package city.spring.modules.system.service.impl;

import city.spring.domain.enums.IdentityType;
import city.spring.modules.ext.EntityExtUtils;
import city.spring.modules.ext.entity.UserGroupExt;
import city.spring.modules.ext.entity.UserPermissionExt;
import city.spring.modules.ext.entity.UserRoleExt;
import city.spring.modules.ext.service.UserGroupExtService;
import city.spring.modules.ext.service.UserPermissionExtService;
import city.spring.modules.ext.service.UserRoleExtService;
import city.spring.modules.personnel.entity.EmployeeEntity;
import city.spring.modules.system.entity.*;
import city.spring.modules.system.repository.UserRepository;
import city.spring.modules.system.service.*;
import city.spring.mq.SmsNoticeTopic;
import city.spring.utils.MyBatisUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.util.BiConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Pageable;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.function.Consumer3;

import java.util.*;
import java.util.function.Function;

/**
 * 用户信息服务
 *
 * @author HouKunLin
 * @date 2019/12/22 0022 0:30
 */
@CacheConfig(cacheNames = {UserServiceImpl.CACHE_NAME})
@Transactional(rollbackFor = Throwable.class)
@Service
public class UserServiceImpl extends ServiceImpl<UserRepository, UserEntity> implements UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
    private final boolean isDebug = logger.isDebugEnabled();
    private final GroupService groupService;
    private final RoleService roleService;
    private final PermissionService permissionService;
    private final UserAccountService userAccountService;
    private final PasswordEncoder passwordEncoder;
    private final SmsNoticeTopic smsNoticeTopic;
    private final UserRoleExtService userRoleExtService;
    private final UserPermissionExtService userPermissionExtService;
    private final UserGroupExtService userGroupExtService;
    private final OrganizationService organizationService;

    public UserServiceImpl(GroupService groupService, RoleService roleService, PermissionService permissionService, UserAccountService userAccountService, PasswordEncoder passwordEncoder, SmsNoticeTopic smsNoticeTopic, UserRoleExtService userRoleExtService, UserPermissionExtService userPermissionExtService, UserGroupExtService userGroupExtService, OrganizationService organizationService) {
        this.groupService = groupService;
        this.roleService = roleService;
        this.permissionService = permissionService;
        this.userAccountService = userAccountService;
        this.passwordEncoder = passwordEncoder;
        this.smsNoticeTopic = smsNoticeTopic;
        this.userRoleExtService = userRoleExtService;
        this.userPermissionExtService = userPermissionExtService;
        this.userGroupExtService = userGroupExtService;
        this.organizationService = organizationService;
    }

    @Override
    public Page<UserEntity> getUsers(Pageable pageable) {
        Page<UserEntity> userEntityPage = MyBatisUtils.toPage(pageable);
        QueryWrapper<UserEntity> userEntityQueryWrapper = new QueryWrapper<>();
        return baseMapper.selectPage(userEntityPage, userEntityQueryWrapper);
    }

    @Cacheable(key = "'getUserByIdOrAccount:'+#idOrAccount")
    @Override
    public UserEntity getUserByIdOrAccount(String idOrAccount) {
        return baseMapper.findByUserIdOrAccountIdentifier(idOrAccount);
    }

    @Cacheable(key = "#idOrAccount+':'+#loadAuthority+':'+#userInfoEnums")
    @Override
    public UserEntity getUserByIdOrAccount(String idOrAccount, boolean loadAuthority, UserInfoEnum... userInfoEnums) {
        UserEntity userEntity = getUserByIdOrAccount(idOrAccount);
        if (userEntity == null) {
            if (isDebug) {
                QueryWrapper<UserAccountEntity> accountEntityQueryWrapper = new QueryWrapper<>();
                accountEntityQueryWrapper.eq("identifier", idOrAccount);
                UserAccountEntity userAccountEntity = userAccountService.getOne(accountEntityQueryWrapper);
                if (userAccountEntity == null) {
                    logger.debug("找不到该用户帐号: {}", idOrAccount);
                    throw new RuntimeException("找不到该帐号");
                }
                logger.debug("找到用户帐号，但是找不到用户，这是一个悬空的用户帐号信息：{}", userAccountEntity);
                throw new RuntimeException("这是一个未绑定用户信息的帐号");
            }
            throw new RuntimeException("找不到该用户");
        }
        if (!"1".equals(userEntity.getId())) {
            this.loadInfos(userEntity, loadAuthority, userInfoEnums);
        } else {
            // 是一个超级管理员，获取所有的权限代码
            String userId = userEntity.getId();
            userEntity.setGroups(groupService.getUserGroupsWithAuthority(userId));
            userEntity.setOrganization(organizationService.getOrganizationInfo(userEntity.getOrgId(), true, true));
            userEntity.setRoles(roleService.list());
            userEntity.setPermissions(permissionService.list());
            userEntity.setAccounts(userAccountService.getUserAccounts(userId));
        }
        return userEntity;
    }

    @Override
    public void updatePassword(String userId, String newPassword) {
        baseMapper.updatePassword(userId, passwordEncoder.encode(newPassword));
    }

    @Override
    public void updatePassword(String userId, String oldPassword, String newPassword) {
        updatePassword(getById(userId), oldPassword, newPassword);
    }

    @Override
    public void updatePassword(UserEntity entity, String oldPassword, String newPassword) {
        if (!passwordEncoder.matches(oldPassword, entity.getPassword())) {
            throw new RuntimeException("密码验证错误");
        }
        baseMapper.updatePassword(entity.getId(), passwordEncoder.encode(newPassword));
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("user", entity);
        map.put("newPassword", newPassword);
        smsNoticeTopic.output().send(MessageBuilder.withPayload(map).build());
    }

    @Override
    public void loadInfos(UserEntity entity, boolean loadAuthority, UserInfoEnum... userInfoEnums) {
        if (entity == null || userInfoEnums.length == 0) {
            return;
        }
        boolean isAllInfo = ArrayUtils.contains(userInfoEnums, UserInfoEnum.ALL);
        boolean isExAccount = ArrayUtils.contains(userInfoEnums, UserInfoEnum.EX_ACCOUNT);

        String entityId = entity.getId();
        Function<String, List<GroupEntity>> groupFunc;
        Function<String, List<RoleEntity>> roleFunc;
        if (loadAuthority) {
            groupFunc = groupService::getUserGroupsWithAuthority;
            roleFunc = roleService::getUserRolesWithAuthority;
        } else {
            groupFunc = groupService::getUserGroups;
            roleFunc = roleService::getUserRoles;
        }
        if (isAllInfo || isExAccount || ArrayUtils.contains(userInfoEnums, UserInfoEnum.GROUP)) {
            entity.setGroups(groupFunc.apply(entityId));
        }
        if (isAllInfo || isExAccount || ArrayUtils.contains(userInfoEnums, UserInfoEnum.UNIT)) {
            if (loadAuthority) {
                entity.setOrganization(organizationService.getOrganizationInfo(entity.getOrgId(), true, true));
            } else {
                entity.setOrganization(organizationService.getOrganizationInfo(entity.getOrgId(), false, false));
            }
        }
        if (isAllInfo || isExAccount || ArrayUtils.contains(userInfoEnums, UserInfoEnum.ROLE)) {
            entity.setRoles(roleFunc.apply(entityId));
        }
        if (isAllInfo || isExAccount || ArrayUtils.contains(userInfoEnums, UserInfoEnum.PERMISSION)) {
            entity.setPermissions(permissionService.getUserPermissions(entityId));
        }
        if (isAllInfo || ArrayUtils.contains(userInfoEnums, UserInfoEnum.ACCOUNT)) {
            entity.setAccounts(userAccountService.getUserAccounts(entityId));
        }
    }

    @Override
    public UserEntity getUserInfo(String primaryKey, boolean loadAuthority, UserInfoEnum... userInfoEnums) {
        UserEntity entity = getById(primaryKey);
        loadInfos(entity, loadAuthority, userInfoEnums);
        return entity;
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void syncUserInfoToUserAccount(UserEntity entity) {
        syncUserAccountAction(entity, (entityFieldFunction, account, identityType) -> {
            String value = entityFieldFunction.apply(entity);
            if (account == null) {
                // 用户信息里面存在：用户名、手机号、电子邮箱，但是在用户帐号里面没有这个帐号，因此要添加一个账号
                if (StringUtils.isBlank(value)) {
                    return;
                }
                // 自动创建这个用户帐号信息
                UserAccountEntity userAccountEntity = new UserAccountEntity();
                userAccountEntity.setUserId(entity.getId());
                userAccountEntity.setIdentifier(value);
                userAccountEntity.setVerified(true);
                userAccountEntity.setIdentityType(identityType);
                userAccountService.save(userAccountEntity);
                return;
            }
            if (!account.getIdentifier().equals(value)) {
                if (StringUtils.isBlank(value)) {
                    userAccountService.lambdaUpdate()
                            .eq(UserAccountEntity::getId, account.getId())
                            .remove();
                    return;
                }
                userAccountService.lambdaUpdate()
                        .set(UserAccountEntity::getIdentifier, value)
                        .eq(UserAccountEntity::getId, account.getId())
                        .update();
            }
        });
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void syncUserAccountToUserInfo(UserEntity entity) {
        syncUserAccountAction(entity, (entityFieldFunction, account, identityType) -> {
            String value = entityFieldFunction.apply(entity);
            if (account != null && !account.getIdentifier().equals(value)) {
                this.lambdaUpdate()
                        .set(entityFieldFunction, account.getIdentifier())
                        .eq(UserEntity::getId, entity.getId())
                        .update();
            }
        });
    }

    /**
     * 判断一个待操作用户的用户名、电子邮件、手机号信息是否存在系统中，如果存在系统中，并且不是待操作用户，则抛出异常。
     * 因为系统允许用户使用用户名、电子邮件、手机号来登录系统，因此这些数据在系统中必须唯一
     *
     * @param entity 需要操作的用户信息
     */
    private void checkExistsUserAccount(UserEntity entity) {
        Set<String> identifiers = new HashSet<>();
        BiConsumer<String, String> checkNewIdentifier = (identifier, message) -> {
            if (identifier == null) {
                return;
            }
            if (!identifiers.add(identifier)) {
                // 新的帐号重复
                throw new RuntimeException(message);
            }
        };
        Consumer3<UserAccountEntity, IdentityType, String> checkDbIdentifier = (findEntity, identityType, message) -> {
            if (findEntity == null) {
                return;
            }
            // 判断是否是当前用户
            if (!findEntity.getUserId().equals(entity.getId())) {
                // 找到的用户ID不是当前操作的用户ID，则判断已经存在该帐号的用户
                throw new RuntimeException(message);
            }
        };
        // 是否存在用户名
        checkNewIdentifier.accept(entity.getUsername(), "用户名与电子邮件或手机号重复");
        // 是否存在电子邮件
        checkNewIdentifier.accept(entity.getEmail(), "电子邮件与用户名或手机号重复");
        // 是否存在手机号
        checkNewIdentifier.accept(entity.getPhone(), "手机号与用户名或电子邮件重复");

        // 是否存在用户名
        checkDbIdentifier.accept(userAccountService.getByIdentifier(entity.getUsername()), IdentityType.USERNAME, "用户名已存在");
        // 是否存在电子邮件
        checkDbIdentifier.accept(userAccountService.getByIdentifier(entity.getEmail()), IdentityType.EMAIL, "电子邮件已存在");
        // 是否存在手机号
        checkDbIdentifier.accept(userAccountService.getByIdentifier(entity.getPhone()), IdentityType.PHONE, "手机号已存在");
    }

    /**
     * 业务处理：保存一个用户信息
     *
     * @param entity 用户信息
     */
    @Override
    public void saveUser(UserEntity entity) {
        // 通过该方法保存的用户设置为系统用户
        entity.setType(0);
        entity.setPassword(passwordEncoder.encode(entity.getPassword()));
        saveOrUpdateBaseInfo(entity);
        // 在前端的 74c81d2c 提交中，修改了用户表单，在用户表单中不再给用户分配角色、权限信息，改为单独的角色、权限分配接口
        // repairRelation(entity);
    }

    @Override
    public void updateUser(UserEntity entity) {
        // 通过该方法保存的用户设置为系统用户
        entity.setType(0);
        // 置空密码字段，不修改密码
        entity.setPassword(null);
        saveOrUpdateBaseInfo(entity);
        // 在前端的 74c81d2c 提交中，修改了用户表单，在用户表单中不再给用户分配角色、权限信息，改为单独的角色、权限分配接口
        // repairRelation(entity);
    }

    @Override
    public void saveOrUpdateBaseInfo(UserEntity entity) {
        checkExistsUserAccount(entity);
        boolean saveOrUpdate = saveOrUpdate(entity);
        if (!saveOrUpdate) {
            throw new RuntimeException("保存用户信息失败");
        }
        syncUserInfoToUserAccount(entity);
    }

    @Override
    public void deleteUser(String primaryKey) {
        if (count() > 1) {
            removeById(primaryKey);
            userAccountService.lambdaUpdate().eq(UserAccountEntity::getUserId, primaryKey).remove();
            userRoleExtService.lambdaUpdate().eq(UserRoleExt::getUserId, primaryKey).remove();
            userPermissionExtService.lambdaUpdate().eq(UserPermissionExt::getUserId, primaryKey).remove();
            userGroupExtService.lambdaUpdate().eq(UserGroupExt::getUserId, primaryKey).remove();
        }
    }

    @Override
    public void deleteUser(List<String> primaryKeys) {
        if (count() > 1) {
            removeByIds(primaryKeys);
            userAccountService.lambdaUpdate().in(UserAccountEntity::getUserId, primaryKeys).remove();
            userRoleExtService.lambdaUpdate().in(UserRoleExt::getUserId, primaryKeys).remove();
            userPermissionExtService.lambdaUpdate().in(UserPermissionExt::getUserId, primaryKeys).remove();
            userGroupExtService.lambdaUpdate().in(UserGroupExt::getUserId, primaryKeys).remove();
        }
    }

    @Override
    public void saveUser(EmployeeEntity entity) {
        UserEntity userEntity = buildBaseUserInfo(entity);
        buildOtherEmployeeInfo(userEntity);
        saveOrUpdateBaseInfo(userEntity);
        if (entity.getId() == null) {
            // 维护ID主键一致
            entity.setId(userEntity.getId());
        }
    }

    @Override
    public void updateUser(EmployeeEntity entity) {
        UserEntity userEntity = buildBaseUserInfo(entity);
        if (lambdaQuery().eq(UserEntity::getId, entity.getId()).count() <= 0) {
            // TODO 这里在后期应该删除掉，保持修改仅仅只是修改，而不是 保存OR修改
            // 系统不存在这个用户信息，通过该方法设置一些新增时的默认数据
            buildOtherEmployeeInfo(userEntity);
        }
        saveOrUpdateBaseInfo(userEntity);
    }

    private UserEntity buildBaseUserInfo(EmployeeEntity entity) {
        UserEntity userEntity = new UserEntity();
        userEntity.setId(entity.getId());
        userEntity.setNickname(entity.getName());
        userEntity.setUsername(entity.getName());
        userEntity.setPhone(entity.getTelPhone());
        userEntity.setAvatar(entity.getAvatarImg());
        // 通过该方法处理的用户设置为员工用户
        userEntity.setType(1);
        return userEntity;
    }

    private void buildOtherEmployeeInfo(UserEntity entity) {
        entity.setIsEnabled(true);
        entity.setIsAccountNonExpired(true);
        entity.setIsAccountNonLocked(true);
        entity.setIsCredentialsNonExpired(true);
        entity.setPassword(passwordEncoder.encode("123456"));
    }

    @Override
    public void setUserRoles(UserEntity entity) {
        EntityExtUtils.repairRelation(userRoleExtService, entity,
                UserEntity::getId, UserEntity::getRoles, RoleEntity::getId,
                UserRoleExt::new, UserRoleExt::getUserId);
    }

    @Override
    public void setUserPermissions(UserEntity entity) {
        EntityExtUtils.repairRelation(userPermissionExtService, entity,
                UserEntity::getId, UserEntity::getPermissions, PermissionEntity::getId,
                UserPermissionExt::new, UserPermissionExt::getUserId);
    }

    /**
     * 抽取通用方法，处理指定帐号类型的信息
     *
     * @param entity     用户
     * @param biConsumer 信息比对处理方法。
     *                   <p>参数一SFunction&lt;UserEntity, String&gt;：用户对象对应的字段get方法</p>
     *                   <p>参数二UserAccountEntity：该用户字段对应的帐号信息，帐号信息可能为null</p>
     *                   <p>参数三IdentityType：这个帐号信息的帐号类型，如果帐号信息为null的时候，可以通过账号类型来创建一个账号信息</p>
     */
    private void syncUserAccountAction(UserEntity entity, Consumer3<SFunction<UserEntity, String>, UserAccountEntity, IdentityType> biConsumer) {
        // 获取用户的帐号列表
        List<UserAccountEntity> userAccounts = userAccountService.getUserAccounts(entity.getId());
        // 从账户列表中获取指定类型的帐号信息
        Function<IdentityType, UserAccountEntity> getUserAccount = (type) -> {
            for (UserAccountEntity userAccount : userAccounts) {
                if (type.equals(userAccount.getIdentityType())) {
                    return userAccount;
                }
            }
            return null;
        };
        // 需要同步的帐号类型，该类型应该要保持与用户信息的字段一致
        IdentityType[] identityTypes = new IdentityType[]{IdentityType.USERNAME, IdentityType.PHONE, IdentityType.EMAIL};
        // 帐号类型所对应的用户字段方法（一一对应）
        List<SFunction<UserEntity, String>> fieldFunctions = Arrays.asList(UserEntity::getUsername, UserEntity::getPhone, UserEntity::getEmail);

        for (int i = 0; i < identityTypes.length; i++) {
            IdentityType identityType = identityTypes[i];
            // 帐号类型对应的获取数据方法
            SFunction<UserEntity, String> fieldFunction = fieldFunctions.get(i);
            // 获取该帐号类型的帐号信息
            UserAccountEntity accountEntity = getUserAccount.apply(identityType);
            biConsumer.accept(fieldFunction, accountEntity, identityType);
        }
    }

    /**
     * 维护 <strong>用户信息</strong> 的 用户组列表、部门列表、角色列表、权限列表
     *
     * @param entity 用户信息
     */
    private void repairRelation(UserEntity entity) {
        EntityExtUtils.repairRelation(userGroupExtService, entity,
                UserEntity::getId, UserEntity::getGroups, GroupEntity::getId,
                UserGroupExt::new, UserGroupExt::getUserId);

        setUserRoles(entity);

        setUserPermissions(entity);
    }
}
